課程:Redux 思維與 RTK 核心 第 1 堂:Redux 思維建立
01狀態管理的必要性
在你開始學習任何 Redux Toolkit 的語法之前,我們必須先問一個最根本的問題:「為什麼我們需要它?」
如果你曾經開發過小型 React 專案,你可能會覺得 useState 和 useEffect 已經綽綽有餘。甚至當專案稍微變大時,React 內建的 Context API 似乎也能解決大部分的問題。那麼,為什麼 Redux 依然是許多大型專案的首選?為什麼我們需要引入一個看起來充滿「樣板程式碼(Boilerplate)」且增加系統複雜度的工具?
這並非因為工程師喜歡把簡單的事情變複雜,而是為了應對當 React 應用程式成長到一定規模後,資料流(Data Flow)會變得混亂且難以預測的本質問題。
消失的資料:Props Drilling 的結構性痛點
讓我們從 React 最核心的設計模式說起。React 是單向資料流(Unidirectional Data Flow),資料總是透過 Props 由父元件傳遞給子元件。
想像一個場景
假設你正在開發一個電商平台。你的應用程式最頂層有一個 App 元件,裡面存儲著「當前登入的使用者資訊(User Object)」。
App需要把這個 User 資訊傳給Header元件。Header裡面有一個Navbar元件。Navbar裡面又有一個UserMenu元件。UserMenu裡面還有一個Avatar元件,這才是最後真正需要顯示使用者頭像的地方。
為了讓 Avatar 顯示圖片,你必須在 Header、Navbar、UserMenu 這些中間元件中,全都寫上 user={user}。
這就是惡名昭彰的 Props Drilling(Props 鑽孔)。
為什麼這是一個問題?
當你只有三層元件時,這看起來只是多打幾個字。但當專案規模擴大到十幾層,或者同一個資料需要被傳遞到樹狀結構的各個角落時,問題就會爆發:
- 維護成本極高:如果你決定把
user物件中的id欄位改名為uid,你可能需要修改 10 個檔案,只為了確保這筆資料能正確「流過」那些根本不關心它的中間元件。 - 元件純粹性被破壞:中間的
Navbar其實完全不需要知道使用者的資訊,它的職責只是排版。但因為 Props Drilling,它被迫承擔了傳遞資料的責任。這使得元件難以被獨立提取或複用。 - 除錯困難:當
Avatar顯示的圖片錯誤時,你必須沿著這條長長的隧道往回找,確認是在哪一層元件、在哪個環節資料被不小心修改或遺失了。
向上提升狀態的侷限:當「神元件」出現
面對 Props Drilling,React 官方文件的建議通常是「Lifting State Up(提升狀態)」。也就是將狀態移動到需要該資料的所有元件的「最近共同祖先」中。
這在初期的確能解決問題,但隨著功能增加,這種做法會遇到嚴重的瓶頸。
1. 頂層元件的臃腫化
如果你的應用程式有許多共享狀態(使用者資訊、購物車內容、佈景主題、通知訊息、權限設定),最終所有的狀態都會被「提升」到最頂層的 App.tsx 或 MainLayout.tsx 中。
這些頂層元件會變成所謂的「神元件(God Component)」。它們管理著數十個 useState,處理著各種複雜的更新邏輯。這導致單一檔案可能突破數千行,邏輯糾纏在一起,只要改動其中一小塊,整個系統都可能受影響。
2. 無關元件的重複渲染(Re-render)效能問題
在 React 的預測機制中,當一個元件的 State 改變時,該元件及其所有的子元件預設都會重新執行渲染函數。
假設你的 App 元件同時管理著「購物車數量」和「使用者名稱」。當使用者僅僅是往購物車裡加了一個東西,App 的 State 更新了,導致整個 App 重新渲染。這意味著即使 UserMenu 完全不需要知道購物車的變化,它也會跟著被重新計算一次。
雖然 React 虛擬 DOM 的機制很快,但在大型應用中,這種無意義的連鎖重新渲染累積起來,會明顯拖慢網頁效能,導致打字卡頓或動畫不流暢。
Context API 是萬靈丹嗎?
有些開發者會說:「我們有 Context API 呀!它不就是為了解決 Props Drilling 而生的嗎?」
沒錯,Context API 確實解決了「傳遞」的問題。它像是一個傳送門,讓你可以在頂層放資料,底層直接接收,繞過中間層。但 Context API 在作為「全域狀態管理工具」時,有幾個致命的侷限。
1. 缺乏精確的訂閱機制
Context 的最大問題在於:只要 Context 的 Value 發生變化,所有使用了 useContext 該 Context 的元件都會被強制重新渲染。
如果你在一個 Context 裡放了 10 個不同的屬性,當屬性 A 改變時,那些只關心屬性 B 的元件也會跟著重新渲染。在 React 中,要優化 Context 的效能非常繁瑣,你可能需要把 Context 切得很碎,或是搭配複雜的 useMemo。
2. 缺乏開發者工具與可追蹤性
當你的狀態變得很複雜,且有多個地方在同時更新狀態時,Context 就像是一個「黑盒子」。你很難知道:
- 是誰在什麼時候修改了資料?
- 資料修改的前後差異是什麼?
- 我們能否「回到過去」看看五秒前的狀態?
在大型專案中,「可預測性」比「方便性」更重要。Context 提供了方便,但在複雜邏輯下,它無法提供像 Redux 那樣強大的開發者工具(Redux DevTools),讓我們能像看錄影帶一樣回放每一次的狀態變更。
我們何時真正需要 Redux?
學習 Redux 並不是為了取代 useState。事實上,大多數的 UI 狀態(例如:表單輸入框的內容、選單是否開啟)依然應該保留在元件內部的 useState 中。
你應該在以下幾種情境下考慮使用 Redux:
- 狀態具有「全球性」且被頻繁使用: 例如使用者的登入狀態、權限設定、全域的載入中(Loading)狀態。這些資料需要在應用的各個角落被讀取,且變動後需要同步更新所有地方。
- 狀態更新邏輯極度複雜: 如果一個狀態的改變涉及到多個步驟,或者取決於另一個狀態的舊值(例如:複雜的撤銷/重做 Undo/Redo 功能,或是像 Trello 那種看板的任務拖拽邏輯),Redux 的 Reducer 模式能將邏輯從 UI 中抽離,讓測試和維護變得容易。
- 需要強大的除錯支援: 如果你在開發一個對資料正確性要求極高的系統(如金融交易介面),Redux 的「時間旅行除錯(Time Travel Debugging)」功能能讓你精確掌握每一筆資料的來龍去脈。
- 團隊協作的需求: Redux 強制執行一套嚴格的規範。雖然初學時覺得麻煩,但這意味著團隊中任何一個成員看到一段 Redux 程式碼,都能立即明白資料流向。它提供了一種「架構上的共識」。
總結
狀態管理的必要性,源於對「軟體複雜度」的控制。React 給了我們強大的 UI 元件化能力,但當元件之間的資料交換變得像一團亂掉的毛線球時,我們需要一套更科學、更可預測的方法來管理這些資料。
Redux 並不神奇,它只是把「狀態」從 React 元件樹中抽離出來,放在一個獨立的容器裡,並規定了一套嚴格的遊戲規則來存取它。
在下一章中,我們將深入探討這套遊戲規則的基石——Redux 的三大原則。理解了這些原則,你就會明白為什麼 Redux 要設計得如此「古怪」,以及它是如何保證狀態永遠是可預測的。
關鍵要點與下一章預告
本章核心回顧
- Props Drilling:跨多層傳遞資料導致中間元件被迫處理無關資訊,增加維護難度與出錯風險。
- Lifting State Up 的極限:狀態提升到頂層會導致元件過於臃腫(神元件),並引發不必要的整棵元件樹重新渲染。
- Context API 的侷限:雖然能繞過中間層,但在效能優化(缺乏精確訂閱)與開發者工具(缺乏可追蹤性)上不如專門的狀態管理庫。
- Redux 的角色:它不是萬靈丹,而是針對複雜、全域、需要高度可預測性狀態的解決方案。
銜接下一個主題
理解了狀態管理的「痛點」後,你可能會想:那 Redux 是怎麼解決這些問題的?它的解決方案是建立在一套嚴謹的哲學之上的。下一章我們將探討 Redux 的三大原則,這將揭示為什麼 Redux 要求我們把所有的狀態都放在同一個地方,以及為什麼它規定你絕對不能直接修改狀態。這些「限制」正是 Redux 強大的來源。